package com.threeti.mecool.web.util;

import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.taglibs.TagLibConfig;
import org.springframework.web.context.support.WebApplicationContextUtils;

/** @author Jay Meng */
public class AccessControlListExtenstionTag extends TagSupport {

  protected static final Logger logger = LoggerFactory.getLogger(AccessControlListExtenstionTag.class);

  private ApplicationContext applicationContext;
  private int secureId;
  private String secureType;
  private Object domainObject;
  private PermissionEvaluator permissionEvaluator;
  private String hasPermission = "";
  private String var;

  // ~ Methods
  // ========================================================================================================

  public int doStartTag() throws JspException {
    if ((null == hasPermission) || "".equals(hasPermission)) {
      return skipBody();
    }

    initializeIfRequired();

    if (domainObject == null && StringUtils.isEmpty(secureType)) {
      if (logger.isDebugEnabled()) {
        logger.debug("domainObject resolved to null, so including tag body");
      }

      // Of course they have access to a null object!
      return evalBody();
    }

    if (SecurityContextHolder.getContext().getAuthentication() == null) {
      if (logger.isDebugEnabled()) {
        logger.debug("SecurityContextHolder did not return a non-null Authentication object, so skipping tag body");
      }

      return skipBody();
    }

    // TODO jay:more reasonable logical check...
    if (!StringUtils.isEmpty(secureType)) {
      if (permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), secureId, secureType,
          hasPermission)) {
        return evalBody();
      }
    } else {
      if (domainObject == null) {
        if (logger.isDebugEnabled()) {
          logger.debug("domainObject resolved to null,just ignore secureType or secureId, so skipping tag body");
        }
        return skipBody();
      }

      if (permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), domainObject,
          hasPermission)) {
        return evalBody();
      }
    }

    return skipBody();
  }

  private int skipBody() {
    if (var != null) {
      pageContext.setAttribute(var, Boolean.FALSE, PageContext.PAGE_SCOPE);
    }
    return TagLibConfig.evalOrSkip(false);
  }

  private int evalBody() {
    if (var != null) {
      pageContext.setAttribute(var, Boolean.TRUE, PageContext.PAGE_SCOPE);
    }
    return TagLibConfig.evalOrSkip(true);
  }

  /**
   * Allows test cases to override where application context obtained from.
   * 
   * @param pageContext
   *          so the <code>ServletContext</code> can be accessed as required by
   *          Spring's <code>WebApplicationContextUtils</code>
   * 
   * @return the Spring application context (never <code>null</code>)
   */
  protected ApplicationContext getContext(PageContext pageContext) {
    ServletContext servletContext = pageContext.getServletContext();

    return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
  }

  public Object getDomainObject() {
    return domainObject;
  }

  public String getHasPermission() {
    return hasPermission;
  }

  private void initializeIfRequired() throws JspException {
    if (applicationContext != null) {
      return;
    }

    this.applicationContext = getContext(pageContext);

    permissionEvaluator = getBeanOfType(PermissionEvaluator.class);
  }

  private <T> T getBeanOfType(Class<T> type) throws JspException {
    Map<String, T> map = applicationContext.getBeansOfType(type);

    for (ApplicationContext context = applicationContext.getParent(); context != null; context = context.getParent()) {
      map.putAll(context.getBeansOfType(type));
    }

    if (map.size() == 0) {
      return null;
    } else if (map.size() == 1) {
      return map.values().iterator().next();
    }

    throw new JspException("Found incorrect number of " + type.getSimpleName() + " instances in "
        + "application context - you must have only have one!");
  }

  public void setDomainObject(Object domainObject) {
    this.domainObject = domainObject;
  }

  public void setHasPermission(String hasPermission) {
    this.hasPermission = hasPermission;
  }

  public void setVar(String var) {
    this.var = var;
  }

  public String getSecureType() {
    return secureType;
  }

  public void setSecureType(String secureType) {
    this.secureType = secureType;
  }

  public int getSecureId() {
    return secureId;
  }

  public void setSecureId(int secureId) {
    this.secureId = secureId;
  }

}
